package org.tri.model;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.table.AbstractTableModel;

/**
 * La clase {@code EntityTableModel} representa un modelo genérico para cualquier clase
 * de entidad, que a partir de una lista de lista de entidades de dicha clase, muestra
 * en las columnas de la tabla ciertas propiedades de la clase (no necesariamente todas).
 * @author kiira
 * @version 1.0, Nov 22, 2011
 */
public class EntityTableModel<K> extends AbstractTableModel {

    /** Filas (objetos) de la tabla. */
    private List<K> filas;
    /** Propiedades de la entidad a mostrar en la tabla. */
    private Field[] propiedades;
    /** Aliases para las propiedades (headers de las columnas). */
    private String[] columnas;

    /**
     * Inicializa el modelo a partir de una clase de entidad, mostrando las propiedades
     * seleccionadas de la lista de entidades.
     * @param clase clase de entidad a modelar.
     * @param filas filas (objetos) del modelo. 
     * @param columnas aliases para las propiedades a mostrar.
     * @param propiedades propiedades de la clase de entidad que se mostraran en cada
     * columna.
     */
    public EntityTableModel( Class<K> clase, List<K> filas, String[] columnas, String[] propiedades ) {
        super();

        //comprobamos que coincida el tamaño de las columnas con las propiedades
        if ( columnas.length != propiedades.length )
            throw new IllegalArgumentException( "La cantidad de columnas debe coincidir con la cantidad de propiedades." );

        //intentamos obtener los campos que se seleccionaron para mostrar
        this.propiedades = new Field[propiedades.length];

        for ( int i = 0; i < propiedades.length; i++ ) {
            try {
                this.propiedades[i] = clase.getDeclaredField( propiedades[i] );
            } catch ( NoSuchFieldException nSFE ) {
                throw new IllegalArgumentException( String.format( "La propiedad %s no existe en la clase %s.",
                                                                   propiedades[i], clase.getName() ) );
            }

            this.propiedades[i].setAccessible( true );

        }//end for

        //cargamos los objetos en la lista
        if ( filas != null )
            this.filas = filas;
        else
            this.filas = new ArrayList( 0 );

        //asignamos los nombres de las columnas
        this.columnas = columnas;

    }//end constructor

    /**
     * Inicializa el modelo a partir de una clase de entidad, las propiedades
     * a mostrar y sus aliases, pero sin ningúna fila.
     * @param clase clase de la entidad a modelar.
     * @param columnas aliases de las propiedades a mostrar.
     * @param propiedades propiedades de la clase de entidad que se mostraran en cada
     * columna.
     */
    public EntityTableModel( Class<K> clase, String[] columnas, String[] propiedades ) {
        this( clase, null, columnas, propiedades );

    }//end constructor

    /**
     * Cambia las filas (objetos) del modelo por un nuevo conjunto.
     * @param filas nuevas filas del modelo.
     */
    public void setFilas( List<K> filas ) {
        if ( filas != null )
            this.filas = filas;
        else
            this.filas = new ArrayList( 0 );

        fireTableDataChanged();

    }//end método setFilas
    
    /**
     * Devuelve las filas del modelo.
     * @return filas del modelo.
     */
    public List<K> getFilas() {
        return filas;
        
    }//end método getFilas
    
    /**
     * Agrega un elemento al modelo.
     * @param fila elemento a agregar.
     */
    public void agregarFila( K fila ) {
        if ( !filas.contains( fila ) )
            filas.add( fila );
        
        fireTableDataChanged();
        
    }//end método agregarFila
    
    /**
     * Quita un elemento del modelo.
     * @param fila elemento a quitar.
     */
    public void quitarFila( K fila ) {
        int row = filas.indexOf( fila );
        
        if ( row != -1 ) {
            filas.remove( row );
        
            fireTableRowsDeleted( row, row );
        
        }
        
    }//end método quitarFila

    /**
     * Devuelve el nombre de la columna representada por el indice 
     * pasado por parámetros.
     * @param column posición (indice) de la columna.
     * @return nombre de la columna indicada.
     */
    @Override
    public String getColumnName( int column ) {
        return columnas[column];

    }//end método getColumnName

    /**
     * Devuelve la cantidad de filas del modelo.
     * @return cantidad de filas del modelo.
     */
    @Override
    public int getRowCount() {
        return filas.size();

    }//end método getRowCount

    /**
     * Devuelve la cantidad de columnas del modelo.
     * @return cantidad de columnas del modelo.
     */
    @Override
    public int getColumnCount() {
        return propiedades.length;

    }//end método getColumnCount

    /**
     * Devuelve el valor de una celda identificada por su fila y columna.
     * @param rowIndex indice de la fila.
     * @param columnIndex índice de la columna.
     * @return valor de la propiedad correspondiente a la columna indicada
     * del objeto correspondiente a la fila indicada.
     */
    @Override
    public Object getValueAt( int rowIndex, int columnIndex ) {
        Object value = null;

        //intentamos obtener el valor de la propiedad en el objeto
        try {
            value = propiedades[columnIndex].get( filas.get( rowIndex ) );
        } catch ( IllegalArgumentException ex ) {
            Logger.getLogger( EntityTableModel.class.getName() ).log( Level.SEVERE, null, ex );
        } catch ( IllegalAccessException ex ) {
            Logger.getLogger( EntityTableModel.class.getName() ).log( Level.SEVERE, null, ex );
        }

        //si el valor es 0 ya sea caracter o numero, reemplaza el valor por una rayita
        if ( value == null ||
             value instanceof Number && ((Number)value).longValue() == 0 ||
             value instanceof Character && ((Character)value) == 0 )
            value = "-";
        
        //si es una fecha le doy el formato dd-MM-yyyy
        if(value instanceof Date){
            SimpleDateFormat formato = new SimpleDateFormat( "dd-MM-yyyy" );
            value = formato.format(value);
        }
        
        return value;

    }//end método getValueAt

    /**
     * Devuelve el objeto que representa una fila del modelo.
     * @param nroFila número de la fila de la cual se desea obtener el objeto.
     * @return objeto de la fila seleccionada o null si dicho objeto no existe.
     */
    public K getFila( int nroFila ) {
        K fila = null;

        if ( nroFila >= 0 && nroFila < filas.size() )
            fila = filas.get( nroFila );

        return fila;

    }//end método getFila

    /**
     * Asigna una lista vacia de filas para limpiar el modelo.
     */
    public void limpiar() {
        this.filas = new ArrayList( 0 );

        fireTableDataChanged();

    }//end método limpiar

}//end clase EntityTableModel

